package za.org.foobar.famon.reporter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import za.org.foobar.famon.Serializer;
import za.org.foobar.famon.dto.CFailure;
import za.org.foobar.famon.dto.CFailureBatch;
import za.org.foobar.famon.dto.CFailureList;
import za.org.foobar.famon.dto.CLoggingEvent;
import za.org.foobar.famon.util.Util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

/**
 *
 */
public class FamonFileBufferWorkProcessor implements FamonWorkProcessor {
    private final Log log = LogFactory.getLog(getClass());
    private FamonReporterLogic logic;

    private String bufferFile = null;
    private int failuresPerBatch = 5;
    private int secondsBetweenReports = 5;
    private int pollSleepSeconds = 300;
    private int pollStartTime = 0;
    private int pollDurationMinutes = 1440;

    public void setFamonReporterLogic(FamonReporterLogic logic) {
        this.logic = logic;
    }

    public void sendFailure(CFailure failure, String threadName, List<CLoggingEvent> loggingEventList,
                            Throwable exception) {
        logic.populateFailure(failure, threadName, loggingEventList, exception);
        saveJSON(failure);
    }

    public Thread getPollingThread() {
        return new Thread(new FamonReporterPollThread(this));
    }

    private synchronized void saveJSON(CFailure failure) {
        String json = Serializer.serializeFailure(failure);
        File f = new File(bufferFile);
        log.info("Writing to file: " + f.getAbsolutePath());
        json += ",\n";
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(f, true);
            out.write(json.getBytes());
        } catch (IOException e) {
            log.error("Could not write to buffer file \"" + bufferFile + "\"", e);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException ignored) {
                }
            }
        }
    }

    public synchronized boolean sendBufferedFailures() {
        String json;
        File originalFile = new File(bufferFile);
        log.debug("Reading from file: " + originalFile.getName());
        if (!originalFile.exists()) {
            log.debug("File does not exist");
            return false;
        }
        File f = new File(bufferFile + ".loading");
        if (f.exists() && !f.delete()) {
            log.error("The loading file " + f.getName() + " exists and could not be deleted");
            return false;
        }
        if (!originalFile.renameTo(f)) {
            log.error("Could not rename buffer file \"" + originalFile.getName() + "\" to loading file \"" + f.getName() + "\"");
            return false;
        }
        try {
            json = Util.readFile(f);
        } catch (FileNotFoundException e) {
            log.debug("JSON buffer file not found, no errors to send");
            return false;
        } catch (IOException e) {
            log.error("Could not read JSON file: " + bufferFile, e);
            return false;
        } finally {
            if (f.exists() && !f.delete()) {
                log.error("Could not delete the buffered file, panic!");
            }
        }
        // Remove any trailing ",\n"
        if (json.length() > 0 && json.endsWith(",\n")) {
            json = json.substring(0, json.length() - 2);
        }
        json = "{failures:[" + json + "]}";

        CFailureList failureList = Serializer.deserializeFailureList(json);
        if (failureList == null) {
            log.debug("No failure batches in JSON");
            return false;
        }
        List<CFailure> list = failureList.getFailures();
        if (list == null || list.size() == 0) {
            log.debug("No failure batches in JSON");
            return false;
        }
        CFailureBatch batch = logic.createCFailureBatch();
        for (int i = 0; i < list.size();) {
            log.debug("Sending " + (i+1) + "-" + (i+failuresPerBatch) + " of " + list.size() + " failures");
            for (int j = 0; j < failuresPerBatch && i < list.size(); j++, i++) {
                batch.addFailure(list.get(i));
            }
            long t = System.currentTimeMillis();
            logic.sendFailureBatch(batch);
            batch.clearFailures();
            t = secondsBetweenReports * 1000 - (System.currentTimeMillis() - t);
            if (t > 20 && i < list.size()) {
                try {
                    log.info("Sleeping " + t + " ms");
                    Thread.sleep(t);
                } catch (InterruptedException ignored) {
                }
            }
        }
        return true;
    }

    public String getBufferFile() {
        return bufferFile;
    }

    public void setBufferFile(String bufferFile) {
        log.debug("bufferFile = \"" + bufferFile + "\"");
        this.bufferFile = Util.emptyAsNull(bufferFile);
    }

    public int getFailuresPerBatch() {
        return failuresPerBatch;
    }

    public void setFailuresPerBatch(int failuresPerBatch) {
        log.debug("failuresPerBatch = \"" + failuresPerBatch + "\"");
        this.failuresPerBatch = failuresPerBatch;
    }

    public int getSecondsBetweenReports() {
        return secondsBetweenReports;
    }

    public void setSecondsBetweenReports(int secondsBetweenReports) {
        log.debug("secondsBetweenReports = \"" + secondsBetweenReports + "\"");
        this.secondsBetweenReports = secondsBetweenReports;
    }

    public int getPollSleepSeconds() {
        return pollSleepSeconds;
    }

    public void setPollSleepSeconds(int pollSleepSeconds) {
        log.debug("pollSleepSeconds = \"" + pollSleepSeconds + "\"");
        this.pollSleepSeconds = pollSleepSeconds;
    }

    public int getPollStartTime() {
        return pollStartTime;
    }

    public void setPollStartTime(int pollStartTime) {
        log.debug("pollStartTime = \"" + pollStartTime + "\"");
        this.pollStartTime = pollStartTime;
    }

    public int getPollDurationMinutes() {
        return pollDurationMinutes;
    }

    public void setPollDurationMinutes(int pollDurationMinutes) {
        log.debug("pollDurationMinutes = \"" + pollDurationMinutes + "\"");
        this.pollDurationMinutes = pollDurationMinutes;
    }
}
